boto3でRDSのwaiterを使用するとき注意したいこと
サーモン大好き、横山です。
waiterって、RDSのインスタンス作成とか実行の長い処理のあとにすることを簡単にかけて出来て便利ですよね。
ただ、思っていた処理と違ってハマってしまったので、メモとして残しておきたいと思います。
目的
今回はRDS(mysql)のスナップショットから復元を行ったあと、マスタパスワードを変更してから、RDS(mysql)にアクセスしたいということで以下のコードを準備しました。
コード
#!/usr/bin/env python # -*- coding: utf-8 -*- import boto3 import sys import datetime from my_func import connect_rds from traceback import print_exc dt_now = datetime.datetime.now() def now_string(dt_format="%Y/%m/%d %H:%M:%S"): return datetime.datetime.now().strftime(dt_format) def output(s, fp=sys.stdout): fp.write('[{}] '.format(now_string())) fp.write(s) fp.write('\n') fp.flush() output_fp = sys.stdout DB_INSTANCE_IDENTIFIER = 'rds_name' DB_SUBNET_GROUPNAME = 'db_subnet_groupname' DB_USER = 'user' DB_NAME = 'dbname' DB_MASTER_PASSWORD = 'password' + dt_now.strftime('%Y%m%d%H%M%S') rds = boto3.client('rds', region_name='ap-northeast-1') output('modify', output_fp) db_modify = rds.modify_db_instance( DBInstanceIdentifier=DB_INSTANCE_IDENTIFIER, ApplyImmediately=True, MasterUserPassword=DB_MASTER_PASSWORD, )['DBInstance'] output('available_waiter', output_fp) available_waiter = rds.get_waiter('db_instance_available') available_waiter.wait( DBInstanceIdentifier=DB_INSTANCE_IDENTIFIER ) output('describe instances', output_fp) db_infos = rds.describe_db_instances( DBInstanceIdentifier=DB_INSTANCE_IDENTIFIER, )['DBInstances'] if db_infos: db_info = db_infos[0] db_address = db_info['Endpoint']['Address'] db_port = db_info['Endpoint']['Port'] db_url = 'mysql://{}:{}@{}:{}/{}'.format(DB_USER, DB_MASTER_PASSWORD, db_address, db_port, DB_NAME) output('connection mysql: {}'.format(db_url), output_fp) try: connect_rds(db_url) #rdsに接続 except: print_exc() output('Done', output_fp)
結果
こちらを実行すると、以下のエラーメッセージが出ました。
$ python main.py [2016/02/09 04:46:47] modify [2016/02/09 04:46:48] available_waiter 2 [2016/02/09 04:46:48] describe instances ... OperationalError: (_mysql_exceptions.OperationalError) (1045, "Access denied for user 'root'@'10.0.xxx.xxx' (using password: YES)") [2016/02/09 04:46:48] Done
検証
結論から言いますと、RDSのマスターパスワードの変更前、もしくは変更中にアクセスしようとして失敗しています。
modify_db_instance
の ApplyImmediately=True
にしていますが、 db_instance_available
のwaiterを実行しても即座に DBInstanceStatus
が変わらず、マスターパスワード変更前にwaiterを抜ける前に抜けてしまっています。
少しコードを変えて、症状を見てみようと思います。
ソースコード
49行目の if db_infos:
のステートメントを以下の様に書き換えました。
if db_infos: while True: import time db_infos = rds.describe_db_instances( DBInstanceIdentifier=DB_INSTANCE_IDENTIFIER, )['DBInstances'] output('DBInstanceStatus: ' + str(db_infos[0]['DBInstanceStatus'])) output('PendingModifiedValues: ' + str(db_infos[0]['PendingModifiedValues'])) time.sleep(5)
実行結果
python chk.py [2016/02/09 04:59:48] modify [2016/02/09 04:59:49] available_waiter 2 [2016/02/09 04:59:49] describe instances [2016/02/09 04:59:49] DBInstanceStatus: available [2016/02/09 04:59:49] PendingModifiedValues: {u'MasterUserPassword': '****'} [2016/02/09 04:59:55] DBInstanceStatus: available [2016/02/09 04:59:55] PendingModifiedValues: {u'MasterUserPassword': '****'} [2016/02/09 05:00:00] DBInstanceStatus: available [2016/02/09 05:00:00] PendingModifiedValues: {u'MasterUserPassword': '****'} [2016/02/09 05:00:05] DBInstanceStatus: available [2016/02/09 05:00:05] PendingModifiedValues: {u'MasterUserPassword': '****'} [2016/02/09 05:00:10] DBInstanceStatus: resetting-master-credentials [2016/02/09 05:00:10] PendingModifiedValues: {u'MasterUserPassword': '****'} [2016/02/09 05:00:15] DBInstanceStatus: resetting-master-credentials [2016/02/09 05:00:15] PendingModifiedValues: {u'MasterUserPassword': '****'} [2016/02/09 05:00:21] DBInstanceStatus: resetting-master-credentials [2016/02/09 05:00:21] PendingModifiedValues: {u'MasterUserPassword': '****'} [2016/02/09 05:00:26] DBInstanceStatus: resetting-master-credentials [2016/02/09 05:00:26] PendingModifiedValues: {u'MasterUserPassword': '****'} [2016/02/09 05:00:31] DBInstanceStatus: resetting-master-credentials [2016/02/09 05:00:31] PendingModifiedValues: {u'MasterUserPassword': '****'} [2016/02/09 05:00:36] DBInstanceStatus: resetting-master-credentials [2016/02/09 05:00:36] PendingModifiedValues: {u'MasterUserPassword': '****'} [2016/02/09 05:00:41] DBInstanceStatus: resetting-master-credentials [2016/02/09 05:00:41] PendingModifiedValues: {u'MasterUserPassword': '****'} [2016/02/09 05:00:47] DBInstanceStatus: resetting-master-credentials [2016/02/09 05:00:47] PendingModifiedValues: {u'MasterUserPassword': '****'} [2016/02/09 05:00:52] DBInstanceStatus: resetting-master-credentials [2016/02/09 05:00:52] PendingModifiedValues: {u'MasterUserPassword': '****'} [2016/02/09 05:00:58] DBInstanceStatus: resetting-master-credentials [2016/02/09 05:00:58] PendingModifiedValues: {u'MasterUserPassword': '****'} [2016/02/09 05:01:04] DBInstanceStatus: resetting-master-credentials [2016/02/09 05:01:04] PendingModifiedValues: {u'MasterUserPassword': '****'} [2016/02/09 05:01:09] DBInstanceStatus: resetting-master-credentials [2016/02/09 05:01:09] PendingModifiedValues: {u'MasterUserPassword': '****'} [2016/02/09 05:01:15] DBInstanceStatus: resetting-master-credentials [2016/02/09 05:01:15] PendingModifiedValues: {u'MasterUserPassword': '****'} [2016/02/09 05:01:21] DBInstanceStatus: available [2016/02/09 05:01:21] PendingModifiedValues: {} [2016/02/09 05:01:26] DBInstanceStatus: available [2016/02/09 05:01:26] PendingModifiedValues: {}
modify_db_instance
を実行して約30秒後に DBInstanceStatus
が available
-> resetting-master-credentials
に変化しています。
この変化の前にwaiterを実行してしまうとすぐに抜けてしまい、結果的にすぐマスターパスワード変更前にアクセスを試みて失敗してします。
改善案
modify_db_instance
後に、 PendingModifiedValues
の値が入っていますので、5秒おきにdescribe_db_instances
を実行し、PendingModifiedValues
が入っており、DBInstanceStatus
の値が available
でなくなったらwaiterを実行するようにします。
ソースコード
if db_infos: db_info = db_infos[0] db_address = db_info['Endpoint']['Address'] db_port = db_info['Endpoint']['Port'] db_url = 'mysql://{}:{}@{}:{}/{}'.format(DB_USER, DB_MASTER_PASSWORD, db_address, db_port, DB_NAME) import time while db_info['PendingModifiedValues']: if db_info['DBInstanceStatus'] != 'available': break output('wait 5s ...') time.sleep(5) db_infos = rds.describe_db_instances( DBInstanceIdentifier=DB_INSTANCE_IDENTIFIER, )['DBInstances'] if db_infos: db_info = db_infos[0] output('available_waiter', output_fp) available_waiter = rds.get_waiter('db_instance_available') available_waiter.wait( DBInstanceIdentifier=DB_INSTANCE_IDENTIFIER ) output('connection mysql: {}'.format(db_url), output_fp) try: connect_rds(db_url) #rdsに接続 except: print_exc() output('Done', output_fp)
実行結果
[2016/02/09 05:36:41] modify [2016/02/09 05:36:41] available_waiter 2 [2016/02/09 05:36:41] describe instances [2016/02/09 05:36:41] wait 5s ... [2016/02/09 05:36:46] available_waiter [2016/02/09 05:38:16] connection mysql: mysql://user:password20160209053641@rds_name.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com:3306/db_name [2016/02/09 05:38:16] Done
まとめ
RDSの modify_db_instance
を実行後に、waiterを実行する場合はご注意ください。
今回の改善案は一例ですので、もっといい方法が有りましたら、是非教えてください!
waiterは用法・容量を守って、正しく使いましょう。